package org.tlang.ast.list.array;

import org.tlang.ast.AST;
import org.tlang.ast.ASTLeaf;
import org.tlang.ast.ASTList;
import org.tlang.metaclass.TSetter;
import org.tlang.context.TypeTable;
import org.tlang.context.ValueTable;
import org.tlang.exception.EvalException;
import org.tlang.exception.TypeException;
import org.tlang.metaclass.TVar;
import org.tlang.type.ArrayType;
import org.tlang.type.IntType;
import org.tlang.type.Type;

import java.util.List;

/**
 * a[1][2]
 */
public class ArrayAccessor extends ASTList implements TSetter {
    public ArrayAccessor(List<AST> children) {
        super(children);
    }

    private String name() {
        return ((ASTLeaf) child(0)).token().getText();
    }

    private int nest() {
        return numChildren() - 1;
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("( ").append(name());
        for (int i = 1; i < numChildren(); i++) {
            stringBuilder.append("[").append(child(i)).append("]");
        }
        stringBuilder.append(" )");
        return stringBuilder.toString();
    }

    @Override
    public Type typeCheck(TypeTable typeTable) throws TypeException {
        Type type = child(0).typeCheck(typeTable);
        if (!(type instanceof ArrayType)) {
            throw new TypeException(name() + " is not array", this);
        }

        // 检查索引的类型是否为int
        for (int i = 1; i < numChildren(); i++) {
            child(i).typeCheck(typeTable).assertTypeOf(IntType.INT_TYPE, child(i));
        }

        ArrayType arrayType = (ArrayType) type;

        if (arrayType.dimension() != nest()) {
            throw new TypeException("bad array dimension: " + nest(), this);
        }

        return arrayType.elementType();
    }

    @Override
    public Object eval(ValueTable valueTable) {
        Object target = child(0).eval(valueTable);
        return eval(valueTable, target, nest(), 0);
    }

    private Object eval(ValueTable valueTable, Object target, int nest, int nestReserved) {
        if (nest == nestReserved) {
            return target;
        }
        if (target instanceof TVar) {
            target = ((TVar) target).get();
        }

        onTargetTypeError(target);

        AST indexAST = child(numChildren() - nest);
        Object index = indexAST.eval(valueTable);
        if (index instanceof TVar) {
            index = ((TVar) index).get();
        }

        onIndexTypeError(index, indexAST);

        Object t = ((Object[]) target)[((Long) index).intValue()];
        return eval(valueTable, t, nest - 1, nestReserved);
    }

    private void onIndexTypeError(Object index, AST ast) {
        if (!(index instanceof Long)) {
            throw new EvalException("the " + ast + " eval result is not number", this);
        }
    }

    private void onTargetTypeError(Object target) {
        if (!(target instanceof Object[])) {
            throw new EvalException("TLObject: " + name() + " is not Array", this);
        }
    }

    @Override
    public void set(ValueTable valueTable, Object value) {
        Object t = child(0).eval(valueTable);
        Object target = eval(valueTable, t, nest(), 1);
        onTargetTypeError(target);

        AST indexAST = child(numChildren() - 1);
        Object lastIndex = indexAST.eval(valueTable);
        if (lastIndex instanceof TVar) {
            lastIndex = ((TVar) lastIndex).get();
        }
        onIndexTypeError(lastIndex, indexAST);

        ((Object[]) target)[((Long) lastIndex).intValue()] = value;
    }
}
